/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package dk.itu.samd.group2.tagapastryClient;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Vector;

import rice.environment.Environment;
import rice.p2p.commonapi.NodeHandle;
//import rice.pastry.NodeHandle;
import rice.p2p.commonapi.rawserialization.RawMessage;
import rice.pastry.NodeIdFactory;
import rice.pastry.PastryNode;
import rice.pastry.PastryNodeFactory;
import rice.pastry.direct.DirectNodeHandle;
import rice.pastry.direct.DirectPastryNodeFactory;
import rice.pastry.direct.EuclideanNetwork;
import rice.pastry.direct.NetworkSimulator;
import rice.pastry.socket.SocketPastryNodeFactory;
import rice.pastry.standard.RandomNodeIdFactory;
import dk.itu.samd.group2.tagapastry.IndexEntry;
import dk.itu.samd.group2.tagapastry.Tag;
import dk.itu.samd.group2.tagapastry.TagapastryClient;
import dk.itu.samd.group2.tagapastry.TagapastryImpl;
import dk.itu.samd.group2.tagapastry.util.Networking;
import dk.itu.samd.group2.tagapastry.util.TagapastrySimulatorListener;

/**
 *
 * @author frgr
 */
public class MyTagapastryClient implements TagapastryClient {

	//private static Tagapastry tagapastry;

	private ArrayList<TagapastryImpl> apps;
	private MessageCollector mc;
	private PastryNodeFactory factory;
	
	// For booting nodes
	//private NodeHandle bootHandle;
	
	// Socket address
	private InetSocketAddress bootaddress;
	
	// Node factory
	private NodeIdFactory nidFactory;
	NetworkSimulator<DirectNodeHandle,RawMessage> sim;
	
	private TagapastrySimulatorListener tim;
	
	private long start;
	private boolean canSend;
	
	private void init(Environment env) {
		this.mc = new MessageCollector();
		start = System.currentTimeMillis();
		// Generate the NodeIds Randomly
		nidFactory = new RandomNodeIdFactory(env);
	}
	
	public void blockSending() {
		this.canSend = false;
	}
	
	public void unblockSending() {
		this.canSend = true;
	}
	
	public boolean canSend() {
		return canSend;
	}
	
	public MyTagapastryClient(int numNodes, Environment env) throws Exception {
		unblockSending();
		init(env);
		
		// Create a new network
		sim = new EuclideanNetwork<DirectNodeHandle,RawMessage>(env);
		
		// Set max speed so its not that aggressive
		//sim.setMaxSpeed(1.0f);
		//sim.setFullSpeed();
		
		// Create new pastry node factory
		factory = new DirectPastryNodeFactory(nidFactory,sim,env);
		
		// build nodes
		buildNodes(numNodes,true);
		
		// Don't limit the simulator anymore.
		// sim.setFullSpeed();
		
		stop(numNodes);
		
		// Wait a few seconds before start routing messages so we make sure
		// that all the nodes were built correctly
		env.getTimeSource().sleep(1000);
		
		tim = new TagapastrySimulatorListener();
		sim.addSimulatorListener(tim);
	}

	public MyTagapastryClient(int bindport, InetSocketAddress bootaddress, int numNodes, Environment env) throws Exception {
		unblockSending();
		init(env);
		
		// construct the PastryNodeFactory, this is how we use rice.pastry.socket
		this.factory = new SocketPastryNodeFactory(nidFactory, bindport, env);
	
		// Make sure we don't use simulator
		// this.bootHandle = null;
		
		// Store boot address
		this.bootaddress = bootaddress;
				
		// build nodes
		buildNodes(numNodes,false);
		
		stop(numNodes);
	}
	
	void stop(int numNodes) {
		long end = System.currentTimeMillis();
		System.out.println("I'm done creating nodes!!. Created " + numNodes);
		System.out.println("It took me: " + (end-start)/1000 + " seconds");
	}
	
	ArrayList<TagapastryImpl> getApps() {
		return this.apps;
	}
	
	public MessageCollector getMsgCollector() {
		if(this.mc == null)
			this.mc = new MessageCollector();
		
		return this.mc;
	}
	
	public TagapastrySimulatorListener getListener() {
		return this.tim;
	}
	
	/*
	 * Unfortunately, current implementation allows us to set up
	 * a threshold the same for every application
	 */
	void setThreshold(int min, int max) {
		for(TagapastryImpl ti : getApps())
			ti.setThreshold(min,max);
	}
	
	private void buildNodes(int numNodes, boolean useSim) throws Exception {

		apps = new ArrayList<TagapastryImpl>(numNodes);
		NodeHandle bootHandle = null;
		// loop to construct the nodes/apps
		int k=0;
		for (int curNode = 0; curNode < numNodes; curNode++) {
			// construct a node, passing the null boothandle on the first loop will cause the node to start its own ring
			PastryNode node = factory.newNode();
			//node.addNetworkListener(new TagapastryNodeListener());

			// construct a new MyApp
			TagapastryImpl app = new TagapastryImpl(node, "myApp", this);
			apps.add(app);
			
			if(useSim) {
				node.boot(bootHandle);
				bootHandle = node.getLocalNodeHandle();
				//bootHandle = node.getLocalHandle();
			} else {
				node.boot(bootaddress);
			}

			// the node may require sending several messages to fully boot into the ring
			synchronized(node) {
				while(!node.isReady() && !node.joinFailed()) {
					// delay so we don't busy-wait
					node.wait(500);

					// abort if can't join
					if (node.joinFailed()) {
						throw new IOException("Could not join the FreePastry ring.  Reason:"+node.joinFailedReason()); 
					}
				}       
			}
			if(k == 1000) {
				System.out.println("Created: 1000 at: " + new java.util.Date());
				k = 0;
			} else k++;
			//System.out.println("Finished creating new node "+node);      
		}
	}
		
	public void startMessaging() {
		TagapastryImpl tagApp = (TagapastryImpl)apps.get(0);

		tagApp.tag("sport", "URLS", "sport.com");
		tagApp.tag("sport", "URLS", "sport.com");
		tagApp.tag("sport", "URLS", "sport.com");
		tagApp.tag("news", "URLS","news.com");
		
		tagApp.search("news sport", "URLS");
	}

	@Override
	public void searchResultsDelivery(Tag tag, Vector<IndexEntry> results) {	
		System.out.println("Received results for tag: " + tag);	
		if(results == null || results.size() == 0) {
			System.out.println("Result set is empty");
			return;
		}
		
		for (IndexEntry entry : results) {
			System.out.println("  " + entry);
		}
	}


	public static void main(String[] args) throws Exception {
		// the number of nodes to use
		final int numNodes = 5;

		try {
			
			runAsSimulator(numNodes);
			//runNormal(numNodes);
			
		} catch (Exception e) {
			// remind user how to use
			System.out.println("Usage:"); 
			System.out.println("java [-cp FreePastry-<version>.jar] rice.tutorial.lesson4.DistTutorial localbindport bootIP bootPort numNodes");
			System.out.println("example java rice.tutorial.DistTutorial 9001 pokey.cs.almamater.edu 9001 10");
			throw e; 
		}
	}
	
	private static void runAsSimulator(int numNodes) throws Exception {
		// We don't need ref to this
		MyTagapastryClient client = new MyTagapastryClient(numNodes, Environment.directEnvironment());
		client.startMessaging();
	}
	
	private static void runNormal(int numNodes) throws Exception {
		// Loads pastry settings
		Environment env = new Environment();
		// disable the UPnP setting (in case you are testing this on a NATted LAN)
		env.getParameters().setString("nat_search_policy","never");
		
		// the port to use locally
		int bindport = 9000; 

		// build the bootaddress from the command line args
		InetAddress bootaddr = Networking.discoverAddress(false, true);

		int bootport = 9000;

		InetSocketAddress bootaddress = new InetSocketAddress(bootaddr,bootport);
		
		// launch our node!
		new MyTagapastryClient(bindport, bootaddress, numNodes, env);
	}


}


